package com.shh.shojbackendjudgeservice.judge;

import cn.hutool.json.JSONUtil;
import com.shh.shojbackendcommon.common.ErrorCode;
import com.shh.shojbackendcommon.exception.BusinessException;
import com.shh.shojbackendjudgeservice.judge.codesandbox.CodeSandbox;
import com.shh.shojbackendjudgeservice.judge.codesandbox.CodeSandboxFactory;
import com.shh.shojbackendjudgeservice.judge.codesandbox.CodeSandboxProxy;
import com.shh.shojbackendjudgeservice.judge.codesandbox.impl.ExampleCodeSandbox;
import com.shh.shojbackendjudgeservice.judge.strategy.JudgeContext;
import com.shh.shojbackendjudgeservice.judge.strategy.JudgeManager;
import com.shh.shojbackendmodel.model.codesandbox.ExecuteCodeRequest;
import com.shh.shojbackendmodel.model.codesandbox.ExecuteCodeResponse;
import com.shh.shojbackendmodel.model.codesandbox.JudgeInfo;
import com.shh.shojbackendmodel.model.dto.question.JudgeCase;
import com.shh.shojbackendmodel.model.entity.Question;
import com.shh.shojbackendmodel.model.entity.QuestionSubmit;
import com.shh.shojbackendmodel.model.enums.JudgeInfoMessageEnum;
import com.shh.shojbackendmodel.model.enums.QuestionSubmitStatusEnum;
import com.shh.shojbackendserviceclient.service.QuestionFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;


import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class JudgeServiceImpl implements JudgeService {

    @Resource
    private QuestionFeignClient questionFeignClient;


    @Resource
    private JudgeManager judgeManager;

    @Value("${CodeSandbox.type}")
    private String type;


    @Override
    public QuestionSubmit doJudge(long questionSubmitId) {
        log.info("进入判题");
        //1.首先获取用户提交数据
        QuestionSubmit questionSubmit = questionFeignClient.getQuestionSubmitById(questionSubmitId);
        if (questionSubmit == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"提交数据不存在");
        }
        Long questionId = questionSubmit.getQuestionId();
        //2.获取题目数据
        Question question = questionFeignClient.getQuestionById(questionId);
        if (question == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"题目数据不存在");
        }
        //3.判断题目状态 如果不是等待状态则直接抛异常 防止用户重复提交 相当于乐观锁
        if(!questionSubmit.getStatus().equals(QuestionSubmitStatusEnum.WAITING.getValue())){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"题目正在判题中，请勿重复提交");
        }

        //4.更新判题信息
        QuestionSubmit updateQuestionSubmit = new QuestionSubmit();
        updateQuestionSubmit.setId(questionSubmitId);
        updateQuestionSubmit.setStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
        boolean updateResult = questionFeignClient.updateQuestionSubmitById(updateQuestionSubmit);
        if(!updateResult){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"更新题目提交状态失败");
        }

        //统一解释  这里疯狂套娃 首先从CodeSandboxFactory获取到一个代码沙箱假如是ExampleCodeSandbox
        //然后又将ExampleCodeSandbox套进了CodeSandboxProxy
        //通过两次套娃得到了codeSandbox
        //首先第一次执行的codeSandbox为CodeSandboxProxy codeSandbox.executeCode会先去执行CodeSandboxProxy中的execute方法
        // 此时进入了CodeSandboxProxy codeSandbox就变为了 ExampleCodeSandbox
        //然后再去执行ExampleCodeSandbox的execute方法
        //这就是代理模式

        //5.调用代码沙箱获取结果 运用了工厂模式
        CodeSandbox codeSandbox = CodeSandboxFactory.newInstance(type);
        //经过代理统一打印日志 用了有参的构造方法
        codeSandbox = new CodeSandboxProxy(codeSandbox);
        //提取用户代码语言
        String language = questionSubmit.getLanguage();
        //提取用户代码
        String code = questionSubmit.getCode();
        //获取题目的输入用例 前端传来的是字符串类型
        String judgeCasestr = question.getJudgeCase();
        //将字符串转换为List类型 便于处理
        List<JudgeCase> judgeCaseList = JSONUtil.toList(judgeCasestr, JudgeCase.class);
        //将inputList转换为List<String>类型 取出input字段转换为一个新的List
        List<String> inputList = judgeCaseList.stream().map(JudgeCase::getInput).collect(Collectors.toList());
        ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
                .code(code)
                .language(language)
                .inputList(inputList)
                .build();
        //调用代码沙箱 这里执行的executeCode是CodeSandboxProxy的方法 会统一打印日志
        ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
        log.info("获取到的代码沙箱响应结果" + executeCodeResponse.toString());
        //代码沙箱的输出结果
        List<String> outputList = executeCodeResponse.getOutputList();
        Integer status = executeCodeResponse.getStatus();

        //5.判题 放入JudgeContext中 由于每一种语言的判题策略都不相同 所以判题的步骤要单独分离出来 根据不同的语言应用不同的判题策略 用上下文Context来传输数据
        JudgeContext judgeContext = new JudgeContext();
        judgeContext.setStatus(status);
        judgeContext.setJudgeInfo(executeCodeResponse.getJudgeInfo());
        judgeContext.setInputList(inputList);
        judgeContext.setOutputList(outputList);
        judgeContext.setJudgeCasesList(judgeCaseList);
        judgeContext.setQuestion(question);
        judgeContext.setQuestionSubmit(questionSubmit);
        log.info("准备判题 代码沙箱的判题信息{} 代码沙箱的输出结果{} 输入的变量是{} 预期的判题输入输出{}",
                executeCodeResponse.getJudgeInfo(),outputList,inputList,judgeCaseList);
        //生成默认的判题策略
        JudgeInfo judgeInfo = judgeManager.doJudge(judgeContext);
        log.info("准备更新的判题信息为 {}",JSONUtil.toJsonStr(judgeInfo));

        //更新用户提交数和通过数  更新
        Question updateQuestion = new Question();
        updateQuestion.setId(questionId);
        updateQuestion.setSubmitNum(question.getSubmitNum() + 1);
        if(judgeInfo.getMessage().equals("Accepted")){
            updateQuestion.setAcceptedNum(question.getAcceptedNum() + 1);
        }
        updateResult = questionFeignClient.updateQuestionById(updateQuestion);
        if(!updateResult){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"更新题目提交状态失败");
        }

        //更新数据库中的结果
        updateQuestionSubmit = new QuestionSubmit();
        updateQuestionSubmit.setId(questionSubmitId);
        updateQuestionSubmit.setStatus(QuestionSubmitStatusEnum.SUCCEED.getValue());
        updateQuestionSubmit.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));
        updateResult = questionFeignClient.updateQuestionSubmitById(updateQuestionSubmit);
        if(!updateResult){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"更新题目提交状态失败");
        }
        QuestionSubmit questionSubmitResult = questionFeignClient.getQuestionSubmitById(questionSubmitId);
        return questionSubmitResult;
    }
}
